package advertising

import (
	"advertising/config/mysql"
	"advertising/config/redis"
	"advertising/internal/app/grpc/client"
	"advertising/internal/app/grpc/protoc/advertisingc"
	"advertising/internal/app/grpc/protoc/devicec"
	"advertising/internal/app/web/service"
	"advertising/internal/dao"
	"advertising/internal/define"
	"advertising/internal/models"
	"advertising/internal/pkg"
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"time"

	"192.168.1.75/go-pkg/helper"
	"192.168.1.75/go-pkg/logx"
	"github.com/samber/lo"
)

// 投放计划终止
func (Server) PublishScheduleEnd(ctx context.Context, req *advertisingc.PublishScheduleEndReq) (*advertisingc.PublishScheduleEndRsp, error) {
	res := &advertisingc.PublishScheduleEndRsp{}
	if req.Id == "" {
		res.Code = -1
		res.Msg = "投放计划不能为空"
		return res, nil
	}
	db := mysql.NewDB()
	scheduleInfo, scheduleCount, _ := dao.PublishSchedule{}.Info(ctx, db, "", req.Id)
	if scheduleInfo == nil || scheduleCount == 0 {
		res.Code = -1
		res.Msg = "投放计划不存在"
		return res, nil
	}
	//投放计划状态判断 【1未提审,2审核中,3投放中,4待开始,5审核失败,6已结束】
	if !lo.Contains([]int8{2, 3, 4}, scheduleInfo.Status) {
		res.Code = -1
		res.Msg = "投放计划当前状态不允许终止"
		return res, nil
	}
	//编辑投放计划信息
	dao.PublishSchedule{}.ChangeSatatus(ctx, db, "", req.Id, []int8{}, 6)
	// 编辑投放设备
	dao.PublishScheduleDevice{}.SetToDelete(ctx, db, req.Id, "", 0)

	// 返回计划关联的设备信息[false 包含待删除]
	PublishScheduleDeviceList, _, _ := dao.PublishScheduleDevice{}.List(ctx, db, "", req.Id, "", int(scheduleInfo.Position), 0, 0, false, 0, 0)
	var deviceList = []struct {
		RobotName    string `json:"robot_name"`
		TerminalType int    `json:"terminal_type"`
	}{}
	for _, v := range PublishScheduleDeviceList {
		deviceList = append(deviceList, struct {
			RobotName    string `json:"robot_name"`
			TerminalType int    `json:"terminal_type"`
		}{v.RobotName, v.TerminalType})
	}
	var devices string
	for _, v := range deviceList {
		devices += "," + v.RobotName + "_" + strconv.Itoa(v.TerminalType)
	}
	devices = strings.TrimPrefix(devices, ",")
	res.Msg = devices
	return res, nil
}

// 投放计划列表
func (Server) PublishScheduleList(ctx context.Context, req *advertisingc.PublishScheduleListReq) (*advertisingc.PublishScheduleListRsp, error) {
	res := &advertisingc.PublishScheduleListRsp{}
	res.Data = &advertisingc.PublishScheduleListRsp_Data{}
	db := mysql.NewDB()
	if req.LocationId != "" || req.DistrictId != "" {
		// 区县商场获取设备
		DeviceAdminClient, clientErr := client.GetDeviceAdminClient()
		if clientErr != nil {
			logx.Error(context.Background(), "connect device admin error", logx.Any("err", clientErr.Error()))
			res.Code = -1
			res.Msg = "系统连接错误"
			return res, nil
		}

		deviceReq := devicec.AllDeviceListReq{
			MerchantId: "1",
			LocationId: req.LocationId,
			DistrictId: req.DistrictId,
		}
		DeviceList, err := DeviceAdminClient.AllDeviceList(context.Background(), &deviceReq)
		if err != nil {
			res.Code = -1
			res.Msg = "获取设备信息错误"
			return res, nil
		}
		var RobotNameList []string
		for _, v := range DeviceList.Data {
			if !lo.Contains(RobotNameList, v.RobotId) {
				RobotNameList = append(RobotNameList, v.RobotId)
			}
		}
		if len(RobotNameList) > 0 {
			req.Devices = req.Devices + "," + strings.Join(RobotNameList, ",")
			req.Devices = strings.TrimPrefix(req.Devices, ",")
		}
	}
	var scheduleIDList []string
	if req.Devices != "" {
		// 先过滤设备，找到设备对应的schedule_id [true 不包含待删除]
		scheduleDeviceList, _, _ := dao.PublishScheduleDevice{}.List(ctx, db, "", "", req.Devices, 0, 0, 0, true, 0, 0)
		for _, v := range scheduleDeviceList {
			scheduleIDList = append(scheduleIDList, v.ScheduleID)
		}
		if len(scheduleIDList) == 0 { // 设备找不到计划，返回空
			return res, nil
		}
	} else {
		if req.LocationId != "" || req.DistrictId != "" {
			return res, nil
		}
	}
	scheduleList, scheduleCount, _ := dao.PublishSchedule{}.List(ctx, db, "", strings.Join(scheduleIDList, ","), req.Name, req.Username, int8(req.Position), int8(req.Status), req.StartTime, req.EndTime, int(req.Page), int(req.Limit), int(req.Platform), req.AmountThresh, "", "", "", "", 0, 0)
	res.Data.Count = scheduleCount
	var PublishScheduleList = []service.PublishScheduleListStruct{}
	for _, v := range scheduleList {
		item := service.PublishScheduleListStruct{}
		item.PublishSchedule = *v
		PublishScheduleList = append(PublishScheduleList, item)
	}
	var ScheduleIDList = []string{}
	for _, v := range PublishScheduleList {
		ScheduleIDList = append(ScheduleIDList, v.ID)
	}

	//投放素材
	var ScheduleMaterial []struct {
		ScheduleID      string `json:"schedule_id"`
		MaterialID      string `json:"material_id"`
		Duration        int64  `json:"duration"`
		PlayType        int32  `json:"play_type"`
		RegularInterval int32  `json:"regular_interval"`
		FilePath        string `json:"file_path"`
		Name            string `json:"name"`
		Type            int32  `json:"type"`
	}
	scheduleMaterialList, _, _ := dao.PublishScheduleMaterial{}.List(ctx, db, strings.Join(ScheduleIDList, ","), "", 0, 0)
	var materialIDList []string
	for _, v := range scheduleMaterialList {
		if !lo.Contains(materialIDList, v.MaterialID) {
			materialIDList = append(materialIDList, v.MaterialID)
		}
		ScheduleMaterial = append(ScheduleMaterial, struct {
			ScheduleID      string `json:"schedule_id"`
			MaterialID      string `json:"material_id"`
			Duration        int64  `json:"duration"`
			PlayType        int32  `json:"play_type"`
			RegularInterval int32  `json:"regular_interval"`
			FilePath        string `json:"file_path"`
			Name            string `json:"name"`
			Type            int32  `json:"type"`
		}{
			ScheduleID:      v.ScheduleID,
			MaterialID:      v.MaterialID,
			Duration:        v.Duration,
			PlayType:        int32(v.PlayType),
			RegularInterval: int32(v.RegularInterval),
		})
	}
	materialList, _, _ := dao.Material{}.List(ctx, db, "", strings.Join(materialIDList, ","), "", []int8{}, []int8{}, "", 0, 0, 0, "")
	var ScheduleMaterialMap = make(map[string][]*advertisingc.PublishScheduleListRsp_Data_Materials)
	var materialMap = make(map[string]struct {
		FilePath string `json:"file_path"`
		Name     string `json:"name"`
		Type     int32  `json:"type"`
	})
	for _, v := range materialList {
		materialMap[v.ID] = struct {
			FilePath string `json:"file_path"`
			Name     string `json:"name"`
			Type     int32  `json:"type"`
		}{
			FilePath: v.FilePath,
			Name:     v.Name,
			Type:     int32(v.Type),
		}
	}
	for _, v := range ScheduleMaterial {
		var filePath string
		var name string
		var Type int32
		if _, ok := materialMap[v.MaterialID]; ok {
			filePath = materialMap[v.MaterialID].FilePath
			name = materialMap[v.MaterialID].Name
			Type = materialMap[v.MaterialID].Type
		}
		ScheduleMaterialMap[v.ScheduleID] = append(ScheduleMaterialMap[v.ScheduleID], &advertisingc.PublishScheduleListRsp_Data_Materials{
			MaterialId:      v.MaterialID,
			Duration:        v.Duration,
			PlayType:        v.PlayType,
			RegularInterval: v.RegularInterval,
			FilePath:        filePath,
			Name:            name,
			Type:            Type,
		})
	}

	//投放设备 [true 不包含待删除的]
	ScheduleDeviceList, _, _ := dao.PublishScheduleDevice{}.List(ctx, db, "", strings.Join(ScheduleIDList, ","), "", 0, 0, 0, true, 0, 0)
	var ScheduleDeviceMap = make(map[string][]string)
	for _, v := range ScheduleDeviceList {
		if !lo.Contains(ScheduleDeviceMap[v.ScheduleID], v.RobotName) { // 设备去重
			ScheduleDeviceMap[v.ScheduleID] = append(ScheduleDeviceMap[v.ScheduleID], v.RobotName)
		}
	}
	for _, v := range PublishScheduleList {
		var item = &advertisingc.PublishScheduleListRsp_Data_ScheduleList{}
		item.Id = v.ID
		item.Name = v.Name
		item.Position = int32(v.Position)
		item.StartTime = v.StartTime
		item.EndTime = v.EndTime
		item.Duration = v.Duration
		item.Details = v.Details
		item.Status = int32(v.Status)
		item.DistrictId = v.DistrictID
		item.LocationId = v.LocationID
		item.MerchantId = v.MerchantID
		item.MerchantName = v.MerchantName
		item.CreateUser = v.CreateUser
		item.CreateUsername = v.CreateUserName
		item.CreatedAt = v.CreatedAt
		item.DeletedAt = v.DeletedAt
		item.PublishType = int32(v.PublishType)
		item.DetailsShow = int32(v.DetailsShow)
		if _, ok := ScheduleMaterialMap[v.ID]; ok {
			item.Materials = ScheduleMaterialMap[v.ID]
		}
		if _, ok := ScheduleDeviceMap[v.ID]; ok {
			item.Devices = ScheduleDeviceMap[v.ID]
		}
		item.DeviceCount = int64(len(item.Devices))

		if v.PublishType == 1 { // 刊例
			item.Orientation = 0 // "-"
		} else if len(strings.Split(v.Gender, ",")) == len(define.GenderMap) && len(strings.Split(v.Age, ",")) == len(define.AgeMap) && len(strings.Split(v.Tag, ",")) == len(define.TagMap) {
			item.Orientation = 1 // 默认
		} else {
			item.Orientation = 2 // 定向
		}
		res.Data.List = append(res.Data.List, item)
	}
	return res, nil
}

// 投放计划详情
func (Server) PublishScheduleDetail(ctx context.Context, req *advertisingc.PublishScheduleDetailReq) (*advertisingc.PublishScheduleDetailRsp, error) {
	res := &advertisingc.PublishScheduleDetailRsp{}
	if req.Id == "" {
		res.Code = -1
		res.Msg = "投放计划id不能为空"
		return res, nil
	}
	scheduleDetail, err := service.PublishSchedule{}.Detail(ctx, req.MerchantId, req.Id)
	if err != nil {
		res.Code = -1
		res.Msg = err.Error()
		return res, nil
	}
	scheduleDetailRsp := &advertisingc.PublishScheduleDetailRsp_PublishScheduleDetail{}
	scheduleDetailRsp.Id = scheduleDetail.ID
	scheduleDetailRsp.Name = scheduleDetail.Name
	scheduleDetailRsp.Position = int32(scheduleDetail.Position)
	scheduleDetailRsp.PublishType = int32(scheduleDetail.PublishType)
	scheduleDetailRsp.PublishCount = scheduleDetail.PublishCount
	scheduleDetailRsp.StartTime = scheduleDetail.StartTime
	scheduleDetailRsp.EndTime = scheduleDetail.EndTime
	scheduleDetailRsp.Duration = scheduleDetail.Duration
	scheduleDetailRsp.Details = scheduleDetail.Details
	scheduleDetailRsp.DetailsShow = int32(scheduleDetail.DetailsShow)
	scheduleDetailRsp.Status = int32(scheduleDetail.Status)
	scheduleDetailRsp.DistrictId = scheduleDetail.DistrictID
	scheduleDetailRsp.LocationId = scheduleDetail.LocationID
	scheduleDetailRsp.Gender = scheduleDetail.Gender
	scheduleDetailRsp.Age = scheduleDetail.Age
	scheduleDetailRsp.Tag = scheduleDetail.Tag
	scheduleDetailRsp.MerchantId = scheduleDetail.MerchantID
	scheduleDetailRsp.MerchantName = scheduleDetail.MerchantName
	scheduleDetailRsp.CreateUser = scheduleDetail.CreateUser
	scheduleDetailRsp.CreateUsername = scheduleDetail.CreateUserName
	scheduleDetailRsp.CreatedAt = scheduleDetail.CreatedAt
	scheduleDetailRsp.DeletedAt = scheduleDetail.DeletedAt
	scheduleDetailRsp.Devices = scheduleDetail.Devices
	for _, v := range scheduleDetail.Materials {
		item := &advertisingc.PublishScheduleDetailRsp_PublishScheduleDetail_Materials{}
		item.MaterialId = v.MaterialID
		item.FilePath = v.FilePath
		item.Name = v.Name
		item.Type = int64(v.Type)
		item.Duration = v.Duration
		item.PlayType = v.PlayType
		item.RegularInterval = v.RegularInterval
		scheduleDetailRsp.Materials = append(scheduleDetailRsp.Materials, item)
	}

	res.Data = scheduleDetailRsp
	return res, nil
}

// 投放计划编辑
func (Server) PublishScheduleSave(ctx context.Context, req *advertisingc.PublishScheduleSaveReq) (*advertisingc.PublishScheduleSaveRsp, error) {
	accessInTime := time.Now()
	db := mysql.NewDB()
	res := &advertisingc.PublishScheduleSaveRsp{}
	res.Code = -1
	if req.Id == "" {
		res.Msg = "投放计划不能为空"
		return res, nil
	}

	scheduleInfo, scheduleCount, _ := dao.PublishSchedule{}.Info(ctx, db, "" /*merchantID*/, req.Id)
	if scheduleInfo == nil || scheduleCount == 0 {
		res.Msg = "投放计划不存在"
		return res, nil
	}
	// 管理端允许修改的状态： 2审核中 3投放中 4待开始
	if scheduleInfo.Status != 2 && scheduleInfo.Status != 3 && scheduleInfo.Status != 4 {
		res.Msg = "投放计划的当前状态不允许编辑"
		return res, nil
	}

	if req.Position == 0 {
		res.Msg = "投放位置不能为空"
		return res, nil
	}
	if req.Position != 1 && req.Position != 2 && req.Position != 3 {
		res.Msg = "投放位置错误"
		return res, nil
	}
	if strings.TrimSpace(req.Name) == "" {
		res.Msg = "计划名称不能为空"
		return res, nil
	}
	regRes, _ := regexp.MatchString("^[0-9a-zA-Z\u4e00-\u9fa5]{1,12}$", strings.TrimSpace(req.Name))
	if !regRes {
		res.Msg = "计划名称仅限中文英文及数字且最长12个字符"
		return res, nil
	}

	// 时间判断
	if req.StartTime == 0 {
		res.Msg = "投放周期开始时间不能为空"
		return res, nil
	}
	if req.EndTime == 0 {
		res.Msg = "投放周期结束时间不能为空"
		return res, nil
	}
	if req.EndTime <= req.StartTime {
		res.Msg = "投放周期结束时间不能小于开始时间"
		return res, nil
	}
	if req.EndTime-req.StartTime > 366*86400 {
		res.Msg = "投放周期最大选择366天"
		return res, nil
	}
	if req.DetailsShow != 0 && req.DetailsShow != 1 {
		res.Msg = "素材详情页开关异常"
		return res, nil
	}

	// 素材判断
	if len(req.Materials) == 0 {
		res.Msg = "投放素材不能为空"
		return res, nil
	}
	var totalDuration int64
	if req.Position == 1 { //前屏
		if scheduleInfo.PublishType == 2 { // 精准
			gender, err1 := service.CheckGender(req.Gender)
			if err1 != nil {
				res.Msg = err1.Error()
				return res, nil
			}
			req.Gender = gender
			age, err1 := service.CheckAge(req.Age)
			if err1 != nil {
				res.Msg = err1.Error()
				return res, nil
			}
			req.Age = age
			tag, err1 := service.CheckTag(req.Tag)
			if err1 != nil {
				res.Msg = err1.Error()
				return res, nil
			}
			req.Tag = tag
			if len(req.Materials) > 1 {
				res.Msg = "投放计划为售卖屏精准时,最多1张素材"
				return res, nil
			}
		} else if scheduleInfo.PublishType == 1 { // 刊例单素材
			if len(req.Materials) > 1 {
				res.Msg = "投放计划为售卖屏单素材刊例时,最多1张素材"
				return res, nil
			}
		} else if scheduleInfo.PublishType == 3 { // 刊例多素材
			if len(req.Materials) > 5 {
				res.Msg = "投放计划为售卖屏多素材刊例时,最多5张素材"
				return res, nil
			}
			req.DetailsShow = 0
			req.Details = ""
		}
		var materialIDList = []string{}
		for _, v := range req.Materials {
			materialIDList = append(materialIDList, v.MaterialId)
		}
		materialList, materialListCount, _ := dao.Material{}.List(ctx, db, "", strings.Join(materialIDList, ","), "", []int8{}, []int8{}, "", 0, 0, 0, "")
		var MaterialMap = make(map[string]models.Material)
		if materialListCount > 0 {
			for _, v := range materialList {
				MaterialMap[v.ID] = *v
			}
		}
		for k, v := range req.Materials {
			if _, ok := MaterialMap[v.MaterialId]; !ok {
				res.Msg = fmt.Sprintf("第%d个素材文件不存在", k+1)
				return res, nil
			}
			if MaterialMap[v.MaterialId].Type == 1 { // 图片
				if MaterialMap[v.MaterialId].Position != int8(req.Position) {
					res.Msg = "当前素材图片不是售卖屏素材图片"
					return res, nil
				}
				if !lo.Contains([]int64{5, 10, 15, 20, 25, 30}, v.Duration) {
					res.Msg = "图片素材投放时长仅限5-30之间的5的倍数"
					return res, nil
				}
				totalDuration += v.Duration
			} else {
				res.Msg = "售卖屏投放计划仅限图片素材"
				return res, nil
			}
		}
	} else if req.Position == 2 { //后屏
		if scheduleInfo.PublishType == 2 { // 精准
			gender, err1 := service.CheckGender(req.Gender)
			if err1 != nil {
				res.Msg = err1.Error()
				return res, nil
			}
			req.Gender = gender
			age, err1 := service.CheckAge(req.Age)
			if err1 != nil {
				res.Msg = err1.Error()
				return res, nil
			}
			req.Age = age
			tag, err1 := service.CheckTag(req.Tag)
			if err1 != nil {
				res.Msg = err1.Error()
				return res, nil
			}
			req.Tag = tag
			if len(req.Materials) > 1 {
				res.Msg = "投放计划为媒体屏精准时,仅可选择1张素材"
				return res, nil
			}
		} else {
			if len(req.Materials) > 5 {
				res.Msg = "投放计划为媒体屏刊例时,最多可选择5张素材"
				return res, nil
			}
		}
		var materialIDList = []string{}
		for _, v := range req.Materials {
			materialIDList = append(materialIDList, v.MaterialId)
		}
		materialList, _, _ := dao.Material{}.List(ctx, db, "", strings.Join(materialIDList, ","), "", []int8{}, []int8{}, "", 0, 0, 0, "")
		var MaterialMap = make(map[string]models.Material)
		for _, v := range materialList {
			MaterialMap[v.ID] = *v
		}
		for k, v := range req.Materials {
			if _, ok := MaterialMap[v.MaterialId]; !ok {
				res.Msg = fmt.Sprintf("第%d个素材文件不存在", k+1)
				return res, nil
			}
			if MaterialMap[v.MaterialId].Type == 1 { // 图片
				if MaterialMap[v.MaterialId].Position != int8(req.Position) {
					res.Msg = "当前素材图片不是后屏素材图片"
					return res, nil
				}
				totalDuration += v.Duration
			} else if MaterialMap[v.MaterialId].Type == 2 { // 视频
				if MaterialMap[v.MaterialId].Duration > 240 || MaterialMap[v.MaterialId].Duration < 4 { // 测试环境临时屏蔽
					res.Msg = "视频素材时长不能超过240秒或小于4秒"
					return res, nil
				}
				req.Materials[k].Duration = MaterialMap[v.MaterialId].Duration // 视频时长以数据库为准
				totalDuration += MaterialMap[v.MaterialId].Duration
			}
		}
	} else if req.Position == 3 { //广告

		if len(req.Materials) > 5 {
			res.Msg = "投放计划为媒体屏刊例时,最多可选择5张素材"
			return res, nil
		}

		var materialIDList = []string{}
		for _, v := range req.Materials {
			materialIDList = append(materialIDList, v.MaterialId)
		}
		materialList, _, _ := dao.Material{}.List(ctx, db, "", strings.Join(materialIDList, ","), "", []int8{}, []int8{}, "", 0, 0, 0, "")
		var MaterialMap = make(map[string]models.Material)
		for _, v := range materialList {
			MaterialMap[v.ID] = *v
		}
		for k, v := range req.Materials {
			if _, ok := MaterialMap[v.MaterialId]; !ok {
				res.Msg = fmt.Sprintf("第%d个素材文件不存在", k+1)
				return res, nil
			}
			if MaterialMap[v.MaterialId].Type == 1 { // 图片
				// if MaterialMap[v.MaterialId].Position != int8(req.Position) {
				// 	res.Msg = "当前素材图片不是后屏素材图片"
				// 	return res, nil
				// }
				totalDuration += v.Duration
			} else if MaterialMap[v.MaterialId].Type == 2 { // 视频
				if MaterialMap[v.MaterialId].Duration > 240 || MaterialMap[v.MaterialId].Duration < 4 { // 测试环境临时屏蔽
					res.Msg = "视频素材时长不能超过240秒或小于4秒"
					return res, nil
				}
				req.Materials[k].Duration = MaterialMap[v.MaterialId].Duration // 视频时长以数据库为准
				totalDuration += MaterialMap[v.MaterialId].Duration
			}
		}
	}

	//设备判断
	DeviceList := strings.Split(req.Devices, ",")
	if req.Devices == "" || len(DeviceList) == 0 {
		res.Msg = "投放设备不能为空"
		return res, nil
	}
	MerchantDeviceList, _ := dao.MerchantPublishDeviceGroup{}.MerchantAuthorizedDevice(ctx, db, scheduleInfo.MerchantID)
	for _, v := range DeviceList {
		if !lo.Contains(MerchantDeviceList, v) {
			res.Msg = fmt.Sprintf("设备【%s】没有投放权限", v)
			return res, nil
		}
	}

	log1Time := time.Since(accessInTime)
	logx.Info(ctx, "计划ID:"+req.Id, logx.Any("log1_time", log1Time.Milliseconds()))
	// 设备时间
	usedDeviceTime := service.PublishSchedule{}.DeviceUsedTime(ctx, req.Devices, req.Id, scheduleInfo.PublishType, int64(req.StartTime), int64(req.EndTime))
	for _, item := range usedDeviceTime {
		if totalDuration+item.TotalDuration > 60*10 {
			res.Msg = fmt.Sprintf("设备【%s】时间已经被占满", item.RobotName)
			return res, nil
		}
	}
	log2Time := time.Since(accessInTime)
	logx.Info(ctx, "计划ID:"+req.Id, logx.Any("log2_time", log2Time.Milliseconds()))

	//编辑投放计划信息
	if scheduleInfo.PublishType != 2 {
		req.Gender = ""
		req.Age = ""
		req.Tag = ""
	}
	var status int8
	if scheduleInfo.Status != 2 { // 状态3，4时开始时间的变动需要修改投放状态
		if int64(req.StartTime) < time.Now().Unix() {
			status = 3 // 投放中
		} else {
			status = 4 // 待开始
		}
	}
	dao.PublishSchedule{}.Update(ctx, db, req.Id, scheduleInfo.MerchantID, strings.TrimSpace(req.Name), int8(req.Position),
		int64(req.StartTime), int64(req.EndTime), totalDuration, pkg.HandlerHtml(req.Details), req.DistrictId,
		req.LocationId, int64(len(DeviceList)), int8(req.DetailsShow), req.Gender, req.Age, req.Tag, status, req.SupportActivityId)

	// ----- 投放素材信息
	var OldMaterialList []string
	materialList, materialCount, _ := dao.PublishScheduleMaterial{}.List(ctx, db, req.Id, "", 0, 0)
	if materialCount > 0 {
		for _, v := range materialList {
			if v.MaterialID != "" {
				OldMaterialList = append(OldMaterialList, v.MaterialID)
			}
		}
	}
	var MaterialIDList []string
	for _, v := range req.Materials {
		MaterialIDList = append(MaterialIDList, v.MaterialId)
	}
	// 新增 删除
	//deleteMaterial  是 materials没有的 即 需删除
	//addMaterial  是 OldMaterialList没有的 即 新增的
	deleteMaterial, addMaterial := lo.Difference(OldMaterialList, MaterialIDList)
	// 编辑
	saveMaterial := lo.Intersect(OldMaterialList, MaterialIDList)

	// 新增
	for _, v := range req.Materials {
		if lo.Contains(addMaterial, v.MaterialId) {
			dao.PublishScheduleMaterial{}.Create(ctx, db, scheduleInfo.MerchantID, req.Id, v.MaterialId, v.Duration, int8(v.PlayType), int64(v.RegularInterval))
		}
	}
	// 编辑
	for _, v := range req.Materials {
		if lo.Contains(saveMaterial, v.MaterialId) {
			dao.PublishScheduleMaterial{}.UpdateDuration(ctx, db, req.Id, v.MaterialId, v.Duration, int8(v.PlayType), int64(v.RegularInterval))
		}
	}
	// 删除
	if len(deleteMaterial) > 0 {
		dao.PublishScheduleMaterial{}.Delete(ctx, db, req.Id, strings.Join(deleteMaterial, ","))
	}

	log3Time := time.Since(accessInTime)
	logx.Info(ctx, "计划ID:"+req.Id, logx.Any("log3_time", log3Time.Milliseconds()))
	// ----- 投放设备信息[true 不包含待删除的]
	PublishScheduleDeviceList, _, _ := dao.PublishScheduleDevice{}.List(ctx, db, "", req.Id, "", int(scheduleInfo.Position), 0, 0, true, 0, 0)
	var OldDeviceList []string
	for _, v := range PublishScheduleDeviceList {
		if !lo.Contains(OldDeviceList, v.RobotName) {
			OldDeviceList = append(OldDeviceList, v.RobotName)
		}
	}

	// 新增 删除
	//deleteDevice  是 DeviceList没有的 即 需删除
	//addDevice  是 OldDeviceList没有的 即 新增的
	deleteDevice, addDevice := lo.Difference(OldDeviceList, DeviceList)
	// 编辑
	saveDevice := lo.Intersect(OldDeviceList, DeviceList)

	// 新增设备
	for _, robotName := range addDevice {
		dao.PublishScheduleDevice{}.Create(ctx, db, scheduleInfo.MerchantID, req.Id, robotName,
			int64(req.StartTime), int64(req.EndTime), int(scheduleInfo.Position))
	}

	// 编辑设备
	db.Model(&models.PublishScheduleDevice{}).
		Where("schedule_id = ?", req.Id).
		Where("robot_name in (?)", saveDevice).
		UpdateColumns(map[string]interface{}{
			"start_time":     req.StartTime,
			"end_time":       req.EndTime,
			"updated_at":     time.Now().Unix(),
			"publish_type":   2, // 编辑
			"publish_time":   0, // 投放时间
			"publish_status": 0, // 未投放
		})

	// 删除设备
	if len(deleteDevice) > 0 {
		dao.PublishScheduleDevice{}.SetToDelete(ctx, db, req.Id, strings.Join(deleteDevice, ","), 0)
	}

	log4Time := time.Since(accessInTime)
	logx.Info(ctx, "计划ID:"+req.Id, logx.Any("log4_time", log4Time.Milliseconds()))
	// 返回设备信息【新增、编辑】，管理端触发及时推送
	NewPublishScheduleDeviceList, _, _ := dao.PublishScheduleDevice{}.List(ctx, db, "", req.Id, "", int(scheduleInfo.Position), 0, 0, false, 0, 0)
	var deviceList = []struct {
		RobotName    string `json:"robot_name"`
		TerminalType int    `json:"terminal_type"`
	}{}
	for _, v := range NewPublishScheduleDeviceList {
		deviceList = append(deviceList, struct {
			RobotName    string `json:"robot_name"`
			TerminalType int    `json:"terminal_type"`
		}{v.RobotName, v.TerminalType})
	}
	var devices string
	for _, v := range deviceList {
		devices += "," + v.RobotName + "_" + strconv.Itoa(v.TerminalType)
	}
	devices = strings.TrimPrefix(devices, ",")
	res.Devices = devices
	res.TotalDuration = int32(totalDuration)
	res.Code = 0
	accessOutTime := time.Since(accessInTime)
	logx.Info(ctx, "计划ID:"+req.Id, logx.Any("accessOutTime", accessOutTime.Milliseconds()))
	return res, nil
}

// 投放计划审核
func (Server) PublishScheduleAudit(ctx context.Context, req *advertisingc.PublishScheduleAuditReq) (*advertisingc.PublishScheduleAuditRsp, error) {
	db := mysql.NewDB()
	res := &advertisingc.PublishScheduleAuditRsp{}
	if req.Id == "" {
		res.Code = -1
		res.Msg = "投放计划不能为空"
		return res, nil
	}
	scheduleInfo, scheduleCount, _ := dao.PublishSchedule{}.Info(ctx, db, "", req.Id)
	if scheduleInfo == nil || scheduleCount == 0 {
		res.Code = -1
		res.Msg = "投放计划不存在"
		return res, nil
	}
	if scheduleInfo.Status != 2 { // 审核中
		res.Code = -1
		res.Msg = "投放计划的当前状态无法审核"
		return res, nil
	}
	if req.AuditStatus != 1 && req.AuditStatus != 2 {
		res.Code = -1
		res.Msg = "审核状态错误"
		return res, nil
	}
	var status int8
	if req.AuditStatus == 1 { // 通过
		status = 4 // 待开始
	} else if req.AuditStatus == 2 { // 拒绝
		status = 5 // 审核失败
	}
	//编辑投放计划状态
	dao.PublishSchedule{}.ChangeSatatus(ctx, db, "", req.Id, []int8{}, status)
	//写入审核记录
	dao.PublishScheduleAuditLog{}.Create(ctx, db, scheduleInfo.MerchantID, scheduleInfo.MerchantName, req.Id, req.MerchantId, req.MerchantName, req.CreateUser, req.CreateUsername, int(req.AuditStatus))

	return res, nil
}

// 投放计划审核记录列表
func (Server) PublishScheduleAuditLogList(ctx context.Context, req *advertisingc.PublishScheduleAuditLogListReq) (*advertisingc.PublishScheduleAuditLogListRsp, error) {
	db := mysql.NewDB()
	var res = &advertisingc.PublishScheduleAuditLogListRsp{}
	tx := db.Table(models.PublishScheduleAuditLog{}.TableName() + " as log").
		Joins("join " + models.PublishSchedule{}.TableName() + " as ps on ps.id = log.schedule_id").
		Where("ps.deleted_at = 0")
	if req.Name != "" {
		tx.Where("ps.name like (?)", "%"+strings.TrimSpace(req.Name)+"%")
	}
	var result = []struct {
		models.PublishSchedule
		AuditMerchantName string `json:"audit_merchant_name" gorm:"column:audit_merchant_name"`
		AuditUsername     string `json:"audit_username" gorm:"column:audit_username"`
		AuditStatus       int    `json:"audit_status" gorm:"column:audit_status"`
		AuditAt           int64  `json:"audit_at" gorm:"column:audit_at"`
	}{}
	res.Data = &advertisingc.PublishScheduleAuditLogListRsp_Data{}
	tx.Count(&res.Data.Count)
	helper.Gorm{}.Paginate(tx, int(req.Page), int(req.Limit)).
		Select("ps.*, log.audit_merchant_name, log.audit_username, log.audit_status, log.audit_at").
		Order("log.audit_at desc").
		Find(&result)
	var scheduleIDList []string
	for _, v := range result {
		scheduleIDList = append(scheduleIDList, v.ID)
	}
	// 投放素材
	ScheduleMaterialList, _ := dao.PublishScheduleMaterial{}.MaterialList(ctx, db, strings.Join(scheduleIDList, ","))
	var ScheduleMaterialMap = make(map[string][]*advertisingc.PublishScheduleAuditLogListRsp_Data_Materials)
	for _, v := range ScheduleMaterialList {
		ScheduleMaterialMap[v.ScheduleID] = append(ScheduleMaterialMap[v.ScheduleID], &advertisingc.PublishScheduleAuditLogListRsp_Data_Materials{
			MaterialId:      v.MaterialID,
			FilePath:        v.FilePath,
			Name:            v.Name,
			Type:            int32(v.Type),
			Duration:        v.Duration,
			PlayType:        int32(v.PlayType),
			RegularInterval: int32(v.RegularInterval),
		})
	}
	// 投放设备
	ScheduleDeviceCount := dao.PublishScheduleDevice{}.ScheduleRobotCount(ctx, db, strings.Join(scheduleIDList, ","))
	var ScheduleDeviceCountMap = make(map[string]int64)
	for _, v := range ScheduleDeviceCount {
		ScheduleDeviceCountMap[v.ScheduleID] = v.DeviceCount
	}

	//投放设备[true 不包含待删除]
	ScheduleDeviceList, _, _ := dao.PublishScheduleDevice{}.List(ctx, db, "", strings.Join(scheduleIDList, ","), "", 0, 0, 0, true, 0, 0)
	var ScheduleDeviceMap = make(map[string][]string)
	for _, v := range ScheduleDeviceList {
		if !lo.Contains(ScheduleDeviceMap[v.ScheduleID], v.RobotName) { // 设备去重
			ScheduleDeviceMap[v.ScheduleID] = append(ScheduleDeviceMap[v.ScheduleID], v.RobotName)
		}
	}
	for _, v := range result {
		var deviceCount int64
		if _, ok := ScheduleDeviceCountMap[v.ID]; ok {
			deviceCount = ScheduleDeviceCountMap[v.ID]
		}
		item := &advertisingc.PublishScheduleAuditLogListRsp_Data_PublishScheduleAuditLogList{
			Id:                v.ID,
			Name:              v.Name,
			Position:          int32(v.Position),
			StartTime:         v.StartTime,
			EndTime:           v.EndTime,
			DistrictId:        v.DistrictID,
			LocationId:        v.LocationID,
			Materials:         ScheduleMaterialMap[v.ID],
			DeviceCount:       deviceCount,
			CreateUsername:    v.CreateUserName,
			MerchantName:      v.MerchantName,
			AuditMerchantName: v.AuditMerchantName,
			AuditUsername:     v.AuditUsername,
			AuditAt:           v.AuditAt,
			AuditStatus:       int32(v.AuditStatus),
		}
		if _, ok := ScheduleDeviceMap[v.ID]; ok {
			item.Devices = ScheduleDeviceMap[v.ID]
		}
		res.Data.List = append(res.Data.List, item)
	}
	return res, nil
}

// 获取投放中计划
// req.Device  robot_name + "_" + terminal_type, 逗号分隔多个
func (Server) GetPublishSchedule(ctx context.Context, req *advertisingc.GetPublishScheduleReq) (*advertisingc.GetPublishScheduleRsp, error) {
	accessInTime := time.Now()
	randomID, _ := helper.UUID{}.String()
	db := mysql.NewDB()
	res := &advertisingc.GetPublishScheduleRsp{}
	var ScheduleDeviceList = []struct {
		ScheduleID   string `json:"schedule_id"`
		RobotName    string `json:"robot_name" gorm:"column:robot_name"`
		TerminalType int    `json:"terminal_type" gorm:"column:terminal_type"`
		PublishType  int    `json:"publish_type" gorm:"column:publish_type"`
		ScheduleType int32  `json:"schedule_type"` //1刊例 2精准
		Gender       string `json:"gender" gorm:"column:gender"`
		Age          string `json:"age" gorm:"column:age"`
		Tag          string `json:"tag" gorm:"column:tag"`
		PublishCount int64  `json:"publish_count" gorm:"column:publish_count"`
		DeviceCount  int64  `json:"device_count" gorm:"column:device_count"`
		Details      string `json:"details" gorm:"column:details"`
		DetailsShow  int32  `json:"details_show" gorm:"column:details_show"`
		DeletedAt    int64  `json:"deleted_at" gorm:"column:deleted_at;"` // 设备是否删除
	}{}

	var list = []*advertisingc.GetPublishScheduleRsp_PublishScheduleData{}
	if !req.Force { // 非强制推送，不指定设备
		req.Devices = ""
	}

	// 获取需要推送的设备
	deviceList := dao.PublishScheduleDevice{}.GetPublishDeviceList(ctx, db, req.Devices)

	var pushDevices []string
	if len(deviceList) > 0 {
		for _, v := range deviceList {
			pushDevices = append(pushDevices, v.RobotName+"_"+strconv.Itoa(v.TerminalType))
			if v.TerminalType == 1 {
				if !lo.Contains(pushDevices, v.RobotName+"_3") {
					pushDevices = append(pushDevices, v.RobotName+"_3")
				}
			}
		}
		// 先拿到设备对应的所有计划,设备后台判断最近是否已经推送
		db.Table(models.PublishSchedule{}.TableName()+" as schedule").
			Joins(" join "+models.PublishScheduleDevice{}.TableName()+" as device on device.schedule_id = schedule.id").
			Where("device.deleted_at = 0").
			Where("schedule.status = 3 and schedule.deleted_at = 0").                          // 计划投放中【3】
			Where("concat(device.robot_name, '_', device.terminal_type) in (?)", pushDevices). // 从最近活跃的设备中筛选
			Group("device.schedule_id, device.robot_name, device.terminal_type").              // 计划 + 设备 + 终端类型
			Select("device.schedule_id, device.robot_name, device.terminal_type, device.publish_type, device.deleted_at, schedule.publish_type as schedule_type, " +
				"schedule.gender, schedule.age, schedule.tag, schedule.publish_count, schedule.device_count, schedule.details, schedule.details_show").
			Find(&ScheduleDeviceList)
		var ScheduleIDList = []string{}
		for _, v := range ScheduleDeviceList {
			ScheduleIDList = append(ScheduleIDList, v.ScheduleID)
		}
		log1Time := time.Since(accessInTime)
		logx.Info(ctx, randomID, logx.Any("log1Time", log1Time.Milliseconds()))
		logx.Info(ctx, "GetPublishSchedule Log", logx.String("req.Devices", req.Devices), logx.Bool("req.Force", req.Force), logx.String("pushDevices", strings.Join(pushDevices, ",")), logx.Int("ScheduleIDList count", len(ScheduleIDList)))
		// 投放计划关联的素材
		ScheduleMaterialList, _ := dao.PublishScheduleMaterial{}.MaterialList(ctx, db, strings.Join(ScheduleIDList, ","))
		var ScheduleMaterialMap = make(map[string][]*advertisingc.GetPublishScheduleRsp_PublishScheduleData_ScheduleMaterial)
		var terminalMap = make(map[string]string)
		for _, v := range ScheduleMaterialList {
			item := &advertisingc.GetPublishScheduleRsp_PublishScheduleData_ScheduleMaterial{}
			item.FilePath = v.FilePath
			item.FileMd5 = v.CheckMd5
			item.FileSize = v.FileSize
			item.Duration = int32(v.Duration)
			item.Position = strconv.FormatInt(int64(v.Position), 10)
			item.MaterialId = v.MaterialID
			item.PlayType = int32(v.PlayType)
			item.RegularInterval = int32(v.RegularInterval)
			ScheduleMaterialMap[v.ScheduleID+"_"+item.Position] = append(ScheduleMaterialMap[v.ScheduleID+"_"+item.Position], item)
			terminalMap[v.ScheduleID] = item.Position
		}

		log2Time := time.Since(accessInTime)
		logx.Info(ctx, randomID, logx.Any("log2Time", log2Time.Milliseconds()-log1Time.Milliseconds()))
		// DeviceToPushMap 为更新投放时间拼装的数组， 1=> 97，2 => 98
		// scheduleID_1 => "DDRetail158,DDRetail159", scheduleID_2 => "DDRetail158,DDRetail159"
		var DeviceToPushMap = make(map[string]string)
		// 拼装数据
		for _, v := range ScheduleDeviceList {
			var TerminalType = ""
			if v.TerminalType == 3 {
				TerminalType = terminalMap[v.ScheduleID]

			} else {
				TerminalType = strconv.Itoa(v.TerminalType)
			}
			item := &advertisingc.GetPublishScheduleRsp_PublishScheduleData{}
			item.RobotName = v.RobotName
			item.TerminalType = int32(v.TerminalType)
			if v.PublishType == 3 { // 删除的设备素材置空
				item.Materials = []*advertisingc.GetPublishScheduleRsp_PublishScheduleData_ScheduleMaterial{}
			} else {
				if _, ok := ScheduleMaterialMap[v.ScheduleID+"_"+TerminalType]; ok {
					for k := range ScheduleMaterialMap[v.ScheduleID+"_"+TerminalType] {
						if v.ScheduleType == 3 { // 刊例多素材 转 刊例
							v.ScheduleType = 1
						}
						ScheduleMaterialMap[v.ScheduleID+"_"+TerminalType][k].ScheduleId = v.ScheduleID
						ScheduleMaterialMap[v.ScheduleID+"_"+TerminalType][k].Gender = v.Gender
						ScheduleMaterialMap[v.ScheduleID+"_"+TerminalType][k].Age = v.Age
						ScheduleMaterialMap[v.ScheduleID+"_"+TerminalType][k].Tag = v.Tag
						ScheduleMaterialMap[v.ScheduleID+"_"+TerminalType][k].ScheduleType = v.ScheduleType
						ScheduleMaterialMap[v.ScheduleID+"_"+TerminalType][k].Details = v.Details
						ScheduleMaterialMap[v.ScheduleID+"_"+TerminalType][k].DetailsShow = v.DetailsShow
						if v.TerminalType == 3 {
							ScheduleMaterialMap[v.ScheduleID+"_"+TerminalType][k].Position = "3"
						}

						if v.DeviceCount != 0 {
							ScheduleMaterialMap[v.ScheduleID+"_"+TerminalType][k].PublishCount = v.PublishCount / v.DeviceCount
						} else {
							ScheduleMaterialMap[v.ScheduleID+"_"+TerminalType][k].PublishCount = 0
						}
					}
					item.Materials = ScheduleMaterialMap[v.ScheduleID+"_"+TerminalType]
				}
			}
			list = append(list, item)
			DeviceToPushMap[v.ScheduleID+"_"+TerminalType] += "," + v.RobotName
		}
		// 以计划id分组设备，减少查询次数
		for key, RobotName := range DeviceToPushMap {
			scheduleID := strings.Split(key, "_")[0]
			TerminalType, _ := strconv.Atoi(strings.Split(key, "_")[1])
			// 更新投放时间;投放状态改为1 => 投放中
			dao.PublishScheduleDevice{}.UpdatePublishTime(ctx, db, scheduleID, strings.TrimPrefix(RobotName, ","), TerminalType, 0, 1, time.Now().Unix())
		}
		log3Time := time.Since(accessInTime)
		logx.Info(ctx, randomID, logx.Any("log3Time", log3Time.Milliseconds()-log2Time.Milliseconds()))
	}
	/*与产品协商，暂时屏蔽，后续10月份针对优化*/
	/*
		var deviceTerminalMap = make(map[string]string)
		for _, v := range pushDevices {
			val := strings.Split(v, "_")
			terminalType, _ := strconv.Atoi(val[1])
			if _, ok := deviceTerminalMap[v]; !ok {
				// 获取易售广告
				YiShouAdvertisingList, _ := service.ThirdApi{}.YiShouAdvertisingList(ctx, v)
				yishouItem := &advertisingc.GetPublishScheduleRsp_PublishScheduleData{}
				yishouItem.RobotName = val[0]
				yishouItem.TerminalType = int32(terminalType)
				for _, adv := range YiShouAdvertisingList {
					yishouItem.Materials = append(yishouItem.Materials, &advertisingc.GetPublishScheduleRsp_PublishScheduleData_ScheduleMaterial{
						ScheduleType: 1,
						FilePath:     strings.Replace(adv.URL, thirdapi.YISHOU_FILE_PREFIX, "/yishou/file", 1),
						FileMd5:      adv.Sign,
						Duration:     int32(adv.ShowTime),
						ScheduleId:   adv.ID, // 唯一id
					})
				}
				if len(yishouItem.Materials) > 0 {
					list = append(list, yishouItem)
				}
				deviceTerminalMap[v] = "1"
			}
		}
	*/
	log4Time := time.Since(accessInTime)
	logx.Info(ctx, randomID, logx.Any("log4Time", log4Time.Milliseconds()))
	res.Total = int64(len(list))
	res.List = list
	logx.Info(ctx, "GetPublishSchedule Log", logx.Any("response list", res.List))
	return res, nil
}

// 投放计划回调
// status 1 执行中 2执行失败 3执行成功 0未投放
func (Server) PublishScheduleCallback(ctx context.Context, req *advertisingc.PublishScheduleCallbackReq) (*advertisingc.PublishScheduleCallbackRsp, error) {
	db := mysql.NewDB()
	if req.Status != 0 { // 0未投放 【离线时触发】
		// 更新投放时间和投放状态，有回复即视为投放成功【3】
		if req.ScheduleId != "" { // 必须知道是哪个投放计划投放成功
			if regRes, _ := regexp.MatchString("^(yishou)?\\d*(,(yishou)?\\d*)*$", req.ScheduleId); regRes {
				dao.PublishScheduleDevice{}.UpdatePublishTime(ctx, db, req.ScheduleId, req.RobotName, int(req.TerminalType), 0, 3, time.Now().Unix())
				// 投放计划中需删除的设备执行删除
				ScheduleDeviceList, _, _ := dao.PublishScheduleDevice{}.List(ctx, db, "", req.ScheduleId, req.RobotName, int(req.TerminalType), 0, 0, false, 0, 0)
				for _, v := range ScheduleDeviceList {
					if v.PublishType == 3 { // 3 待删除
						dao.PublishScheduleDevice{}.Delete(ctx, db, v.ID, "", "", 0)
					}
				}
				// 触发易售广告播放通知
				for _, v := range strings.Split(req.ScheduleId, ",") {
					if strings.HasPrefix(v, "yishou") {
						rdb := redis.NewDB()
						trackURL, _ := rdb.Get(ctx, define.RedisYiShouTrackPrefix+req.ScheduleId).Result()
						if trackURL != "" {
							http.Get(trackURL)
						}
					}
				}
			}
		}
	}
	var res = &advertisingc.PublishScheduleCallbackRsp{}
	return res, nil
}

// 设备上报播放次数 [单设备对应计划的总播放次数]
func (Server) AdvertisingShowCount(ctx context.Context, req *advertisingc.AdvertisingShowCountReq) (*advertisingc.AdvertisingShowCountRsp, error) {
	res := &advertisingc.AdvertisingShowCountRsp{}
	// 检验投放计划
	db := mysql.NewDB()
	ScheduleInfo, scheduleCount, _ := dao.PublishSchedule{}.Info(ctx, db, "", req.ScheduleId)
	if scheduleCount == 0 {
		return res, nil
	}
	// 检验设备[true 不包含待删除]
	_, deviceCount, _ := dao.PublishScheduleDevice{}.List(ctx, db, "", req.ScheduleId, req.RobotName, int(req.TerminalType), 0, 0, true, 0, 0)
	if deviceCount == 0 {
		return res, nil
	}

	// req.Count  单设备对应计划的总播放次数
	if ScheduleInfo.DeviceCount != 0 {
		if (int64(req.Count)) > (ScheduleInfo.PublishCount / ScheduleInfo.DeviceCount) {
			res.Code = -1
			res.Msg = "投放计划总次数已经用完"
			return res, nil
		}
	}

	// 删除日志
	scheduleLogList, count, _ := dao.PublishScheduleLog{}.List(ctx, db, req.ScheduleId, req.RobotName, 0, 0)
	if count == 0 { // 新增
		dao.PublishScheduleLog{}.Create(ctx, db, req.ScheduleId, ScheduleInfo.MerchantID, req.RobotName, int64(req.Count))
	} else { // 编辑
		dao.PublishScheduleLog{}.UpdateCount(ctx, db, req.ScheduleId, req.RobotName, int(req.Count))
	}
	if ScheduleInfo.DeviceCount == 0 {
		return res, nil
	}
	// 验证总次数
	var TotalUsedCount int64
	for _, v := range scheduleLogList {
		TotalUsedCount += v.Count
	}

	// 编辑投放计划的已使用次数
	dao.PublishSchedule{}.ChangePublishUsedCount(ctx, db, req.ScheduleId, int(TotalUsedCount))
	// 投放结束终止投放计划
	if TotalUsedCount >= ScheduleInfo.PublishCount {
		service.PublishSchedule{}.End(ctx, db, ScheduleInfo.MerchantID, ScheduleInfo.ID)
	}
	return res, nil
}

// 广告节目清单
func (Server) AdvertisingMaterialList(ctx context.Context, req *advertisingc.AdvertisingMaterialListReq) (*advertisingc.AdvertisingMaterialListRsp, error) {
	res := &advertisingc.AdvertisingMaterialListRsp{}
	db := mysql.NewDB()
	MaterialList, _ := dao.PublishScheduleDevice{}.GetDeivcePublishScheduleMaterialList(ctx, db, req.RobotName, int(req.TerminalType))
	for _, v := range MaterialList {
		item := &advertisingc.AdvertisingMaterialListRsp_MaterialList{}
		item.ScheduleId = v.ScheduleID
		item.MaterialId = v.MaterialID
		item.MaterialName = v.MaterialName
		item.MaterialFile = v.MaterialFile
		item.MaterialType = v.MaterialType
		item.Sort = v.Sort
		item.ScheduleName = v.ScheduleName
		item.StartTime = v.StartTime
		item.EndTime = v.EndTime
		item.Duration = v.Duration
		item.CreateUser = v.CreateUser
		item.CreateUsername = v.CreateUsername
		item.Gender = v.Gender
		item.Age = v.Age
		item.Tag = v.Tag
		item.PublishType = v.PublishType
		item.Status = v.Status
		item.Orientation = v.Orientation
		res.List = append(res.List, item)
	}
	res.Total = int64(len(res.List))
	return res, nil
}

// 广告清单排序
func (Server) AdvertisingMaterialSort(ctx context.Context, req *advertisingc.AdvertisingMaterialSortReq) (*advertisingc.AdvertisingMaterialSortRsp, error) {
	res := &advertisingc.AdvertisingMaterialSortRsp{}
	db := mysql.NewDB()
	scheduleList, _ := dao.PublishScheduleDevice{}.GetDeivcePublishScheduleList(ctx, db, req.RobotName, int(req.TerminalType))
	scheduleIDList := []string{}
	for _, v := range scheduleList {
		scheduleIDList = append(scheduleIDList, v.ID)
	}

	var MaterialIDList = []string{}
	MaterialList, _ := dao.PublishScheduleMaterial{}.MaterialList(ctx, db, strings.Join(scheduleIDList, ","))
	for _, v := range MaterialList {
		MaterialIDList = append(MaterialIDList, v.ID)
	}
	for _, v := range req.Sort {
		if lo.Contains(MaterialIDList, v.ScheduleMaterialId) {
			dao.PublishScheduleMaterial{}.Sort(ctx, db, v.ScheduleMaterialId, int8(v.Sort))
		} else {
			res.Code = -1
			res.Msg = "找不到素材"
			return res, nil
		}
	}
	return res, nil
}

// 广告播放清单上报
func (Server) AdvertisingPlayListUpload(ctx context.Context, req *advertisingc.AdvertisingPlayListUploadReq) (*advertisingc.AdvertisingPlayListUploadRsp, error) {
	db := mysql.NewDB()
	// 删除旧数据
	dao.PublishScheduleDevicePlaylist{}.Delete(ctx, db, req.RobotName, int(req.TerminalType), "")
	// 写入新数据
	var actualList = []string{}
	for _, v := range req.Materials {
		if !strings.HasPrefix(v.ScheduleId, "yishou") {
			dao.PublishScheduleDevicePlaylist{}.Create(ctx, db, req.RobotName, int(req.TerminalType), v.ScheduleId, v.MaterialId, v.
				FilePath, v.FileMd5)
			actualList = append(actualList, v.ScheduleId+"_"+v.MaterialId)
		}
	}
	// 当前素材列表
	MaterialList, _ := dao.PublishScheduleDevice{}.GetDeivcePublishScheduleMaterialList(ctx, db, req.RobotName, int(req.TerminalType))
	var expectedList = []string{} // 投放计划id_素材id
	for _, v := range MaterialList {
		expectedList = append(expectedList, v.ScheduleID+"_"+v.MaterialID)
	}
	actualAdd, actualReduce := lo.Difference(actualList, expectedList)
	if len(actualAdd) > 0 || len(actualReduce) > 0 {
		// 推送告警
		alarmMsg := map[string]interface{}{
			"alarm_type": 3001, // 广告投放异常
			"robot_id":   req.RobotName,
			"created_at": time.Now().Unix(),
		}
		json.Marshal(alarmMsg) //jsonString
		// rdb := redis.NewDB()
		// rdb.LPush(ctx, define.RedisAlarmNotifyList, string(jsonString))
		logx.Info(ctx, "advertising alarm notify", logx.String("robot_name", req.RobotName), logx.Int("terminal_type", int(req.TerminalType)), logx.Any("add", actualAdd), logx.Any("reduce", actualReduce))
	}
	res := &advertisingc.AdvertisingPlayListUploadRsp{}
	res.Code = 0
	return res, nil
}

// 广告播放清单列表获取
func (Server) AdvertisingPlayList(ctx context.Context, req *advertisingc.AdvertisingPlayListReq) (*advertisingc.AdvertisingPlayListRsp, error) {
	db := mysql.NewDB()
	res := &advertisingc.AdvertisingPlayListRsp{}

	list, count, _ := dao.PublishScheduleDevicePlaylist{}.List(ctx, db, "", req.RobotName, int(req.TerminalType), int(req.Page), int(req.Limit))

	var scheduleIDList = []string{}
	var materialIDList = []string{}
	for _, v := range list {
		if !lo.Contains(scheduleIDList, v.ScheduleID) {
			scheduleIDList = append(scheduleIDList, v.ScheduleID)
		}
		if !lo.Contains(materialIDList, v.MaterialID) {
			materialIDList = append(materialIDList, v.MaterialID)
		}
	}
	scheduleIDNameList, _ := dao.PublishSchedule{}.GetScheduleNameList(ctx, db, strings.Join(scheduleIDList, ","))
	var scheduleIDNameMap = make(map[string]string)
	for _, v := range scheduleIDNameList {
		scheduleIDNameMap[v.ID] = v.Name
	}
	materialIDNameList, _ := dao.Material{}.GetMaterialNameList(ctx, db, strings.Join(materialIDList, ","))
	var materialIDNameMap = make(map[string]string)
	for _, v := range materialIDNameList {
		materialIDNameMap[v.ID] = v.Name
	}

	for _, v := range list {
		item := &advertisingc.AdvertisingPlayListRsp_MaterialList{}
		item.ScheduleId = v.ScheduleID
		if scheduleName, ok := scheduleIDNameMap[v.ScheduleID]; ok {
			item.ScheduleName = scheduleName
		}
		item.MaterialId = v.MaterialID
		if materialName, ok := materialIDNameMap[v.MaterialID]; ok {
			item.MaterialName = materialName
		}
		item.MaterialFile = v.FilePath
		item.CreatedAt = int32(v.CreatedAt)
		res.List = append(res.List, item)
	}
	res.Code = 0
	res.Total = count
	return res, nil
}

// 机器广告重置
func (Server) RobotAdvertisingReset(ctx context.Context, req *advertisingc.RobotAdvertisingResetReq) (*advertisingc.RobotAdvertisingResetRsp, error) {
	res := &advertisingc.RobotAdvertisingResetRsp{}
	if req.RobotId == "" {
		res.Code = -1
		res.Msg = "设备必填"
		return res, nil
	}
	// 投放设备状态修改
	dao.PublishScheduleDevice{}.SetToDelete(ctx, mysql.NewDB(), "", req.RobotId, 0)

	// 提醒设备后台
	deviceAdminClient, clientErr := client.GetDeviceAdminClient()
	if clientErr != nil {
		logx.Error(ctx, "connnect error", logx.Any("error", "device_admin client connect error"))
		res.Code = -1
		res.Msg = "设备后台连接错误"
		return res, nil
	}
	var params devicec.NoticePublishScheduleChangeReq
	params.Devices = req.RobotId + "_1," + req.RobotId + "_2"
	deviceAdminClient.NoticePublishScheduleChange(ctx, &params)

	res.Code = 0
	res.Msg = ""
	return res, nil
}
